package pl.maciejziarko.mshare.service;

import org.apache.shiro.crypto.hash.Sha256Hash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import pl.maciejziarko.mshare.domain.Account;
import pl.maciejziarko.mshare.persistence.AccountMapper;
import pl.maciejziarko.mshare.security.SaltGenerator;
import pl.maciejziarko.mshare.service.exception.AccountNotFoundException;
import pl.maciejziarko.mshare.service.exception.AccountServiceException;
import pl.maciejziarko.mshare.service.exception.EmailUsedException;
import pl.maciejziarko.mshare.service.exception.UsernameTakenException;

import java.util.Date;
import java.util.List;

/**
 * @author Maciej Ziarko
 */
@Component
public class MShareAccountService implements AccountService {

    private static final Logger LOG = LoggerFactory.getLogger(MShareAccountService.class);

    private AccountMapper accountMapper;

    private SaltGenerator saltGenerator;

    @Autowired
    public MShareAccountService(AccountMapper accountMapper, SaltGenerator saltGenerator) {
        this.accountMapper = accountMapper;
        this.saltGenerator = saltGenerator;
    }

    @Override
    public void addAccount(Account account) throws UsernameTakenException, EmailUsedException, AccountServiceException {
        try {
            //Processing:
            String salt = saltGenerator.generateSalt();
            Sha256Hash hash = new Sha256Hash(account.getPassword(), salt);
            account.setHashedPassword(hash.toHex());
            account.erasePlaintextPassword();
            account.setSalt(salt);
            account.setRegistration(new Date());

            //Persistence:
            int rowsAffected = accountMapper.insertAccount(account);

            if (rowsAffected != 1) {
                LOG.debug("Rows affected != 1");

                throw new AccountServiceException();
            }

        } catch (DuplicateKeyException e) {
            LOG.debug(e.getMessage());

            String message = e.getMessage();
            if (message.contains("accounts_pkey")) {
                throw new UsernameTakenException("Username [{}] is already taken.", account.getUsername());
            } else if (message.contains("accounts_email_key")) {
                throw new EmailUsedException("Email [{}] is already taken.", account.getEmail());
            } else {
                throw new AccountServiceException();
            }
        } catch (DataAccessException e) {
            LOG.debug(e.getMessage());

            throw new AccountServiceException();
        }
    }

    @Override
    public void removeAccount(String username) throws AccountNotFoundException, AccountServiceException {
        //TODO
        throw new AssertionError("Not implemented yet.");
    }

    @Override
    public void editAccount(Account account) throws EmailUsedException, AccountServiceException {
        try {
            accountMapper.updateAccount(account);
        } catch (DuplicateKeyException e) {
            throw new EmailUsedException("Email [{}] is already taken.", account.getEmail());
        } catch (DataAccessException e) {
            throw new AccountServiceException();
        }
    }

    @Override
    public Account getAccount(String username) throws AccountNotFoundException, AccountServiceException {
        try {
            Account account = accountMapper.selectAccount(username);
            checkNullResult(username, account);
            return account;
        } catch (DataAccessException e) {
            throw new AccountServiceException();
        }
    }

    @Override
    public List<Account> getAllAccounts() throws AccountServiceException {
        try {
            return accountMapper.selectAllAccounts();
        } catch (DataAccessException e) {
            throw new AccountServiceException();
        }
    }

    @Override
    public void removeAllAccounts() throws AccountServiceException {
        //TODO
        throw new AssertionError("Not implemented yet.");
    }

    @Override
    public Account getCredentials(String username) throws AccountNotFoundException, AccountServiceException {
        try {
            Account credentials = accountMapper.selectCredentials(username);
            checkNullResult(username, credentials);
            return credentials;
        } catch (DataAccessException e) {
            LOG.debug(e.getMessage());
            throw new AccountServiceException();
        }
    }

    @Override
    public long countAccounts() throws AccountServiceException {
        try {
            return accountMapper.selectAccountsCount();
        } catch (DataAccessException e) {
            throw new AccountServiceException();
        }
    }

    private void checkNullResult(String username, Account credentials) throws AccountNotFoundException {
        if (credentials == null) {
            throw new AccountNotFoundException("Account with username [{}] is not found.", username);
        }
    }
}
